#region License

// Copyright (c) 2008, BXO Solutions AB
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, 
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright 
//       notice, this list of conditions and the following disclaimer in the 
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the BXO Solutions AB nor the names of its 
//       contributors may be used to endorse or promote products derived from 
//       this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Bxo.TestingPortal.Entities;
using Bxo.TestingPortal.WebApplication.Controllers;
using Castle.MonoRail.Framework;
using NUnit.Framework;
using NUnit.Framework.Extensions;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Commons;
using Rhino.Commons.ForTesting;

namespace Bxo.TestingPortal.WebApplication.Tests {
    [TestFixture]
    public class ScenarioControllerFixture : InProjectControllerTest {
        #region Setup/Teardown

        [SetUp]
        public void SetUp() {
            UnitOfWork.Start();
            DatabaseTestFixtureBase.CurrentContext.SetupDatabase(UnitOfWork.CurrentSession);

            this.testee = new ScenarioController();
        }

        [TearDown]
        public void TearDown() {
            UnitOfWork.Current.Dispose();
        }

        #endregion

        [TestFixtureSetUp]
        public void TestFixtureSetUp() {
            DatabaseTestFixtureBase.IntializeNHibernateAndIoC(PersistenceFramework.ActiveRecord,
                                                              "Windsor.boo",
                                                              DatabaseEngine.SQLite,
                                                              MappingInfo.FromAssemblyContaining<Project>());
        }

        private ScenarioController testee;

        private void AssertThatScenarioIsNotSet() {
            Assert.That(this.Testee.PropertyBag["scenario"], Is.Null, "Scenario must not be passed to the view.");
        }

        private void AssertThatScenarioIsSet(Scenario scenario) {
            Assert.That(this.Testee.PropertyBag["scenario"], Is.EqualTo(scenario), "Problem with the scenario passed to the view.");
        }

        protected override Controller Testee {
            get { return this.testee; }
        }

        [Test]
        public void Add_RendersErrorViewWhenNoProject() {
            this.PrepareController(this.testee, "scenario", "add");

            this.testee.Add(null);

            this.AssertThatProjectIsNotSet();
            this.AssertThatViewIsSelected("Add_NoProject");
        }

        [Test]
        public void Add_RendersViewCorrectly() {
            this.PrepareController(this.testee, "scenario", "add");

            int projectId;

            using (new DataPreparationScope()) {
                var project = new Project("project");
                Repository<Project>.Save(project);
                projectId = project.Id;
            }

            this.testee.Add(Repository<Project>.Get(projectId));

            this.AssertThatProjectIsSet(Repository<Project>.Get(projectId));
        }

        [Test]
        public void AddCommit_AddScenarioWithBasicPropertiesOnly() {
            this.PrepareController(this.testee, "scenario", "addcommit");

            int projectId;

            using (new DataPreparationScope()) {
                var project = new Project("project");
                Repository<Project>.Save(project);
                projectId = project.Id;
            }

            this.testee.AddCommit(Repository<Project>.Get(projectId), "some_summary", "some_instruction", new string[0]);

            Project actualProject = Repository<Project>.Get(projectId);

            Assert.That(actualProject.Scenarios.Count(), Is.EqualTo(1), "Invalid number of scenarios in project.");
            Scenario addedScenario = actualProject.Scenarios.First();
            Assert.That(addedScenario, Is.Not.Null);
            Assert.That(addedScenario.CurrentVersion.Summary, Is.EqualTo("some_summary"), "Invalid summary.");
            Assert.That(addedScenario.CurrentVersion.Instruction, Is.EqualTo("some_instruction"), "Invalid instruction.");

            // View.
            this.AssertThatScenarioIsSet(addedScenario);
            this.AssertThatProjectIsSet(actualProject);
            this.AssertThatViewIsSelected("AddCommit");
        }

        [Test]
        public void AddCommit_AddsScenarioWhenBothProjectCategoriesSpecified() {
            this.PrepareController(this.testee, "scenario", "addcommit");

            int projectId;

            using (new DataPreparationScope()) {
                var project = new Project("project") {
                        Categories = {
                                new Category("category_1"),
                                new Category("category_2")
                        }
                };
                Repository<Project>.Save(project);
                projectId = project.Id;
            }

            this.testee.AddCommit(Repository<Project>.Get(projectId), "summary", "steps", new[] {"1", "2"});

            Project actualProject = Repository<Project>.Get(projectId);

            Scenario addedScenario = actualProject.Scenarios.First();
            Assert.That(addedScenario.Categories, Is.EquivalentTo(Repository<Category>.FindAll().ToList()));

            // View.
            this.AssertThatScenarioIsSet(addedScenario);
            this.AssertThatProjectIsSet(actualProject);
            this.AssertThatViewIsSelected("AddCommit");
        }

        [Test]
        public void AddCommit_AddsScenarioWhenHalfProjectCategoriesSpecified() {
            this.PrepareController(this.testee, "scenario", "addcommit");

            int projectId;
            int categoryId;

            using (new DataPreparationScope()) {
                var category = new Category("category_2");
                var project = new Project("project") {
                        Categories = {
                                new Category("category_1"),
                                category
                        }
                };
                Repository<Project>.Save(project);
                projectId = project.Id;
                categoryId = category.Id;
            }

            this.testee.AddCommit(Repository<Project>.Get(projectId), "summary", "steps", new[] {categoryId.ToString()});

            Project actualProject = Repository<Project>.Get(projectId);

            Scenario addedScenario = actualProject.Scenarios.First();
            Assert.That(addedScenario.Categories, Is.EquivalentTo(new[] {Repository<Category>.Get(categoryId)}));

            // View.
            this.AssertThatScenarioIsSet(addedScenario);
            this.AssertThatProjectIsSet(actualProject);
            this.AssertThatViewIsSelected("AddCommit");
        }

        [Test]
        public void AddCommit_InstructionIsNull_EmptyInstructionIsSet() {
            this.PrepareController(this.testee, "scenario", "addcommit");

            int projectId;

            using (new DataPreparationScope()) {
                var project = new Project("project");
                Repository<Project>.Save(project);
                projectId = project.Id;
            }

            this.testee.AddCommit(Repository<Project>.Get(projectId), "some_summary", null, new string[0]);

            Project actualProject = Repository<Project>.Get(projectId);

            Assert.That(actualProject.Scenarios.Count(), Is.EqualTo(1), "Invalid number of scenarios in project.");
            Scenario addedScenario = actualProject.Scenarios.First();
            Assert.That(addedScenario, Is.Not.Null);
            Assert.That(addedScenario.CurrentVersion.Instruction, Is.EqualTo(string.Empty), "Invalid instruction.");

            // View.
            this.AssertThatScenarioIsSet(addedScenario);
            this.AssertThatProjectIsSet(actualProject);
            this.AssertThatViewIsSelected("AddCommit");
        }

        [Test]
        public void Edit_RendersViewCorrectly() {
            this.PrepareController(this.testee, "scenario", "edit");

            int scenarioId;

            using (new DataPreparationScope()) {
                var project = new Project("project");

                var categories = new[] {new Category("category1"), new Category("category2"), new Category("category3")};
                project.Categories.Add(categories[0]);
                project.Categories.Add(categories[1]);
                project.Categories.Add(categories[2]);

                var scenario = new Scenario(new ScenarioVersion("summary", string.Empty));
                project.Scenarios.Add(scenario);
                scenario.Categories.Add(categories[0]);
                scenario.Categories.Add(categories[2]);

                Repository<Project>.Save(project);

                scenarioId = scenario.Id;
            }

            this.testee.Edit(Repository<Scenario>.Get(scenarioId));

            Scenario actualScenario = Repository<Scenario>.Get(scenarioId);

            // Form.
            Assert.That(this.testee.PropertyBag["summary"], Is.EqualTo(actualScenario.CurrentVersion.Summary));
            Assert.That(this.testee.PropertyBag["instruction"], Is.EqualTo(actualScenario.CurrentVersion.Instruction));
            Assert.That(this.testee.PropertyBag["categories"], Is.EquivalentTo(new List<string>(from c in actualScenario.Categories select c.Id.ToString(CultureInfo.InvariantCulture))));

            // View.
            this.AssertThatScenarioIsSet(actualScenario);
            this.AssertThatProjectIsSet(actualScenario.Project);
            this.AssertThatViewIsSelected("Edit");
        }

        [Test]
        public void EditCommit_ViewIsRenderedProperly() {
            this.PrepareController(this.testee, "Scenario", "EditCommit");

            int scenarioId;

            using (new DataPreparationScope()) {
                var project = new Project("project");
                var scenario = new Scenario(new ScenarioVersion("summary", "instruction"));
                project.Scenarios.Add(scenario);
                Repository<Project>.Save(project);
                scenarioId = scenario.Id;
            }

            Scenario actualScenario = Repository<Scenario>.Get(scenarioId);

            this.testee.EditCommit(Repository<Scenario>.Get(scenarioId), "summary", "instruction", null);

            this.AssertThatProjectIsSet(actualScenario.Project);
            this.AssertThatScenarioIsSet(actualScenario);
            this.AssertThatViewIsSelected("EditCommit");
        }

        [RowTest]
        [Row("summary", "instruction", "new_summary", "new_instruction")]
        [Row("summary", "instruction", "new_summary", "instruction")]
        [Row("summary", "instruction", "summary", "new_instruction")]
        public void EditCommit_WhenVersionedPropertiesAreChangedThenScenarioIsUpdated(string oldSummary, string oldInstruction, string newSummary, string newInstruction) {
            this.PrepareController(this.testee, "Scenario", "EditCommit");

            int scenarioId;

            using (new DataPreparationScope()) {
                var scenario = new Scenario(new ScenarioVersion(oldSummary, oldInstruction));
                Repository<Project>.Save(new Project("a_project") {Scenarios = {scenario}});
                scenarioId = scenario.Id;
            }

            this.testee.EditCommit(Repository<Scenario>.Get(scenarioId), newSummary, newInstruction, null);

            Scenario actualScenario = Repository<Scenario>.Get(scenarioId);

            Assert.That(actualScenario.Versions, Has.Count(2), "New version has not been added.");
            Assert.That(actualScenario.CurrentVersion.Summary, Is.EqualTo(newSummary));
            Assert.That(actualScenario.CurrentVersion.Instruction, Is.EqualTo(newInstruction));
        }

        [Test]
        public void EditCommit_WhenVersionedPropertiesAreUnchangedThenScenarioIsNotUpdated() {
            this.PrepareController(this.testee, "Scenario", "EditCommit");

            int scenarioId;

            using (new DataPreparationScope()) {
                var scenario = new Scenario(new ScenarioVersion("summary", "instruction"));
                Repository<Project>.Save(new Project("a_project") {Scenarios = {scenario}});
                scenarioId = scenario.Id;
            }

            this.testee.EditCommit(Repository<Scenario>.Get(scenarioId), "summary", "instruction", null);

            Scenario actualScenario = Repository<Scenario>.Get(scenarioId);

            Assert.That(actualScenario.Versions, Has.Count(1), "New version has been added.");
        }

        [Test]
        public void EditCommit_NewCategoriesAreSpecified_CategoriesChange() {
            this.PrepareController(this.testee, "Scenario", "EditCommit");

            int scenarioId;
            int categoryBarsId;
            int categoryBazsId;

            using (new DataPreparationScope()) {
                var scenario = new Scenario(new ScenarioVersion("summary", "instruction"));
                var categoryFoos = new Category("foos");
                var categoryBars = new Category("bars");
                var categoryBazs = new Category("bazs");
                var project = new Project("a_project") {
                        Scenarios = {scenario},
                        Categories = {categoryFoos, categoryBars, categoryBazs}
                };
                scenario.Categories.Add(categoryFoos);
                scenario.Categories.Add(categoryBars);
                Repository<Project>.Save(project);
                scenarioId = scenario.Id;
                categoryBarsId = categoryBars.Id;
                categoryBazsId = categoryBazs.Id;
            }

            this.testee.EditCommit(Repository<Scenario>.Get(scenarioId), "summary", "instruction", new[] {categoryBarsId.ToString(CultureInfo.InvariantCulture), categoryBazsId.ToString(CultureInfo.InvariantCulture)});

            Scenario actualScenario = Repository<Scenario>.Get(scenarioId);

            Assert.That(actualScenario.Categories.Count, Is.EqualTo(2), "Scenario has invalid number of categories.");
            Assert.That(actualScenario.Categories.Contains(Repository<Category>.Get(categoryBarsId)), "Scenario does not belong to category 'bars'.");
            Assert.That(actualScenario.Categories.Contains(Repository<Category>.Get(categoryBazsId)), "Scenario does not belong to category 'bazs'.");
        }

        [Test]
        public void Show_RendersErrorViewWhenNoScenario() {
            this.PrepareController(this.testee, "scenario", "show");

            this.testee.Show(null);

            this.AssertThatScenarioIsNotSet();
            this.AssertThatProjectIsNotSet();
            this.AssertThatViewIsSelected("Show_NoScenario");
        }

        [Test]
        public void Show_RendersViewCorrectly() {
            this.PrepareController(this.testee, "scenario", "show");

            int scenarioId;

            using (new DataPreparationScope()) {
                var project = new Project("project");
                var scenario = new Scenario(new ScenarioVersion("scenario", string.Empty));
                project.Scenarios.Add(scenario);
                Repository<Project>.Save(project);

                scenarioId = scenario.Id;
            }

            this.testee.Show(Repository<Scenario>.Get(scenarioId));

            Scenario actualScenario = Repository<Scenario>.Get(scenarioId);

            this.AssertThatScenarioIsSet(actualScenario);
            this.AssertThatProjectIsSet(actualScenario.Project);
            this.AssertThatViewIsSelected("Show");
        }
    }
}